﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using ThreeByte.ActiveDeck.Service;
using ThreeByte.ActiveDeck.Data.UserResponse;
using System.IO;
using ThreeByte.Media;
using System.Windows.Threading;
using System.Windows;
using ThreeByte.ActiveDeck.Config;
using Excel = Microsoft.Office.Interop.Excel;
using log4net;
using System.Xml.Linq;
using System.ComponentModel;
using ThreeByte.ActiveDeck.Publisher.Client;
using ThreeByte.ActiveDeck.Publish.Model;
using System.Net;

namespace ThreeByte.ActiveDeck.AddIn.Content.UserResponse
{
    public abstract class UserResponseFeedbackUpdater : IDisposable, INotifyPropertyChanged
    {
        private static readonly ILog log = LogManager.GetLogger(typeof(UserResponseFeedbackUpdater));

        protected string PresentationID;
        protected int Section;
        protected int Page;
        protected bool IsGrouped;
        protected PowerPoint.Shape MyShape;
        protected WebServiceClient Client;
        protected CoreConfig Config;
        protected Dictionary<int, int> Pages;

        private static readonly TimeSpan NOW = TimeSpan.FromSeconds(0);
        private static readonly TimeSpan NEVER = TimeSpan.FromMilliseconds(-1);
        private static readonly TimeSpan POLL_INTERVAL = TimeSpan.FromSeconds(3);

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string info) {
            if(PropertyChanged != null) {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        //All updaters start off inactive
        private bool _active = false;
        public bool Active {
            get {
                return _active;
            }
            set {
                bool modified = (_active != value);
                _active = value;
                if(modified && (PropertyChanged != null)){
                    NotifyPropertyChanged("Active");
                }
            }
        }

        public event EventHandler DataUpdated;
        
        private XElement _currentUserData;
        public XElement CurrentUserData {
            get {
                return _currentUserData;
            }
            private set {
                bool modified = false;
                if(value == _currentUserData){
                    modified = false;
                } else if(!(value == _currentUserData)
                    || (value == null && _currentUserData != null)
                    || (value != null && _currentUserData == null)
                    || (value.ToString() != _currentUserData.ToString())) {
                    modified = true;
                }
                _currentUserData = value;
                if(modified) {
                    NotifyPropertyChanged("CurrentUserData");
                }
            }
        }

        private XElement _currentGroupedUserData;
        public XElement CurrentGroupedUserData {
            get { return _currentGroupedUserData; }
            private set {
                bool modified = false;
                if(value == _currentGroupedUserData) {
                    modified = false;
                } else if(!(value == _currentGroupedUserData)
                    || (value == null && _currentGroupedUserData != null)
                    || (value != null && _currentGroupedUserData == null)
                    || (value.ToString() != _currentGroupedUserData.ToString())) {
                        modified = true;
                }

                _currentGroupedUserData = value;
                if(modified) {
                    NotifyPropertyChanged("CurrentGroupedUserData");
                }
            }
        }

        private System.Threading.Timer RefreshTimer;
        private ActivePublisherClient UpdateClient;

        public UserResponseFeedbackUpdater(string presentation, PowerPoint.Shape shape, WebServiceClient client, CoreConfig cConfig, bool isGrouped):this(presentation, shape, client, isGrouped) {
            Config = cConfig;

            if(IsGrouped) {
                string configs = MyShape.Tags["Pages"];
                if(configs != "") {
                    string[] allConfigs = configs.Split('|');

                    List<MultipleChoiceConfig> mcConfigs = new List<MultipleChoiceConfig>();

                    Pages = new Dictionary<int, int>();

                    foreach(string c in allConfigs) {
                        string[] sectionAndPage = c.Split(',');
                        MultipleChoiceConfig mcConfig = (MultipleChoiceConfig)Config[int.Parse(sectionAndPage[0])][int.Parse(sectionAndPage[1])];
                        mcConfigs.Add(mcConfig);

                        if(mcConfig.HasAnswer) {
                            for(int i = 0; i < mcConfig.Answers.Count; i++) {
                                if(mcConfig.Answers[i].IsCorrect) {
                                    Pages.Add(mcConfig.PageId, i + 1);
                                }
                            }
                        }
                    }
                }
            }
        }

        public UserResponseFeedbackUpdater(string presentation, PowerPoint.Shape shape, WebServiceClient client, bool isGrouped = false) {
            PresentationID = presentation;
            Section = int.Parse(shape.Tags["Section"]);
            Page = int.Parse(shape.Tags["Page"]);
            IsGrouped = isGrouped;

            
            MyShape = shape;
            Client = client;

            RefreshTimer = new System.Threading.Timer(RefreshCallback, null, NOW, NEVER);
            UpdateClient = new ActivePublisherClient(ServiceConfiguration.SYNC_SERVICE_ENDPOINT, client.Credential);

            Subscription newSubs = new Subscription()
            {
                From = 0,
                Channel = String.Format("{0}_UD_{1}_{2}", PresentationID, Section, Page),
                OnMessageReceived = MessageReceived,
                OnError = ErrorReceived
            };

            UpdateClient.SubscribeAsync(newSubs);
            
            
        }

        public void MessageReceived(Subscription sub, ActiveMessage msg) {
            log.Debug("Refreshing User Data");
            string[] channel = msg.Channel.Split('_');
            PresentationID = channel[0];
            Section = Int32.Parse(channel[2]);
            Page = Int32.Parse(channel[3]);

            UpdateResponseFeedback(PresentationID, Section, Page);
            UpdateClient.SubscribeAsync(sub);
        }

        private void UpdateResponseFeedback(string presentationID, int section, int page) {
            if(Active) {
                try {
                    if(!IsGrouped) {
                        CurrentUserData = Client.GetUserData(presentationID, section, page).Element(ServiceConfiguration.NAMESPACE + "Aggregate");
                        if(UpdateUserData(CurrentUserData) || firstUpdate) {
                            UpdateGraphic();
                            firstUpdate = false;
                            if(DataUpdated != null) {
                                DataUpdated(this, EventArgs.Empty);
                            }
                        }
                    } else {
                        CurrentGroupedUserData = Client.GetMultiQuestionGroupedUserData(PresentationID, Pages);//.Element(ServiceConfiguration.NAMESPACE + "UserData");
                        if(UpdateUserData(CurrentGroupedUserData) || firstUpdate) {
                            UpdateGraphic();
                            firstUpdate = false;
                            if(DataUpdated != null) {
                                DataUpdated(this, EventArgs.Empty);
                            }
                        }
                    }
                    
                } catch(Exception ex) {
                    log.Error("Error refreshing user data", ex);
                }
            }
        }

        public void ErrorReceived(Subscription sub, Exception ex) {
            if (ex is InvalidOperationException) {
                InvalidOperationException wex = (InvalidOperationException)ex;
                log.Error("Error received on channel " + sub.Channel, wex);                
            }

            

            UpdateClient.SubscribeAsync(sub);
        }

        private bool firstUpdate = true;
        public void RefreshCallback(object state) {
            log.Debug("Refreshing User Data");
            UpdateTimer(RefreshTimer, NEVER, NEVER);

            UpdateResponseFeedback(PresentationID, Section, Page);            
            UpdateTimer(RefreshTimer, POLL_INTERVAL, NEVER);
        }

        private void UpdateTimer(System.Threading.Timer timer, TimeSpan dueTime, TimeSpan period) {
            lock(_timerLock) {
                if(_disposed) {
                    return;
                }
                timer.Change(dueTime, period);
            }

        }


        protected abstract bool UpdateUserData(XElement userData);
        protected abstract void UpdateGraphic();

        private bool _disposed = false;
        private object _timerLock = new object();
        public virtual void Dispose() {
            try {
                if(_disposed) {
                    throw new ObjectDisposedException("Cannot Dispose of UserResponseFeedbackUpdater twice");
                }
                _disposed = true;
                log.DebugFormat("Disposing: {0}", this.GetType());
                lock(_timerLock) {
                    RefreshTimer.Change(TimeSpan.FromMilliseconds(-1), TimeSpan.FromMilliseconds(-1));
                    RefreshTimer.Dispose();
                }

            } catch(Exception ex) {
                log.Error("Catching a Dispose error", ex);
                throw;
            }
        }
    }
}
